/**
 * Does nothing.
 *
 * @class Container for functions relating to objects.
 */
Util.Object = function()
{
};

/**
 * Returns the names of an object's properties as an array. Ignores properties
 * found on any object.
 */
Util.Object.names = function(obj)
{
	var names = [];
	var bare = {};
	
	// JavaScript doesn't really have a hash or dictionary type, only a
	// generic object type. This is a problem because the variables object
	// we're given can have properties that are intrinsic to objects which
	// shouldn't be added to the query string. To work around this, we
	// create a bare object and ignore any properties in variables that are
	// also found on the bare object.
	
	for (var name in obj) {
		if (name in bare)
			continue;
		names.push(name);
	}
	
	return names;
}

/**
 * Calls the given function once per property in the object. The function
 * should accept the property's name as the first argument and its value as
 * the second.
 */
Util.Object.enumerate = function(obj, func, thisp)
{
	if (!thisp)
		var thisp = null;
	
	Util.Object.names(obj).each(function (name)
	{
		func.call(thisp, name, obj[name]);
	});
}

/**
 * Clones (creates a copy of) the given object.
 */
Util.Object.clone = function(some_object)
{
	var new_obj;
	
	if (!some_object || typeof(some_object) != 'object')
		return some_object;
	
	try {
		new_obj = new some_object.constructor();
	} catch (e) {
		new_obj = new Object();
	}
	
	for (var name in some_object) {
		new_obj[name] = some_object[name];
	}
	
	return new_obj;
}

/**
 * Determines if two objects are equal.
 */
Util.Object.equal = function(a, b)
{
	if (typeof(a) != 'object') {
		return (typeof(b) == 'object')
			? false
			: (a == b);
	} else if (typeof(b) != 'object') {
		return false;
	}
	
	seen = {};
	
	for (var name in a) {
		if (!(name in b && Util.Object.equal(a[name], b[name])))
			return false;
		seen[name] = true;
	}
	
	for (var name in b) {
		if (!(name in seen))
			return false;
	}
	
	return true;
}

/**
 * Pops up a window whose contents are generated by get_print_r_chunk, q.v.
 *
 * @param	obj				the object to print_r
 * @param	max_deepness	(optional) how many levels of parameters to automatically open. Defaults to 1.
 * @return					a UL element which has as descendents a representation of the given object
 */
Util.Object.print_r = function(obj, max_deepness)
{
	var alert_win = new Util.Window;
	alert_win.open('', '_blank', 'status=1,scrollbars=1,resizable,width=600,height=300');
	var print_r_chunk = Util.Object.get_print_r_chunk(obj, alert_win.document, alert_win, max_deepness);
	alert_win.body.appendChild(print_r_chunk);
};

/**
 * Generates a UL element which has as descendents a representation of
 * the given object. The representation is similar to that exposed by
 * PHP's print_r or pray.
 *
 * @param	obj				the object to print_r
 * @param	doc_obj			(optional) the document object with which to create the print_r chunk. 
 *                          Defaults to the document refered to by <code>document</code>.
 * @param	max_deepness	(optional) how many levels of parameters to automatically open. Defaults to 1.
 * @return					a UL element which has as descendents a representation of the given object
 */
Util.Object.get_print_r_chunk = function(obj, doc_obj, win, max_deepness)
{
	if ( doc_obj == null )
	{
		doc_obj = document;
	}

	if ( max_deepness == null )
	{
		max_deepness = 1;
	}


	/**
	 * Displays or hides the properties of a property of an object being
	 * print_r'd. Should be called only when a click event is fired by the
	 * appropriate element in the print_r window.
	 *
	 * @param	event	The event object passed onclick
	 */
	var open_or_close_print_r_ul = function(event, variable)
	{
		event = event == null ? win.event : event;
		var span_elem = event.currentTarget == null ? event.srcElement : event.currentTarget;

		// If open, close
		if ( span_elem.nextSibling != null )
		{
			//alert('open, so close (nextSibling =' + span_elem.nextSibling);
			while ( span_elem.nextSibling != null )
				span_elem.parentNode.removeChild(span_elem.nextSibling);
		}
		// Else (if closed), open
		else
		{
			//alert('closed, so open (variable:' + variable + '); span_elem:' + span_elem);
			span_elem.parentNode.appendChild(
				Util.Object.get_print_r_chunk(variable, span_elem.ownerDocument, 1)
			);
		}
	};


	var ul_elem = doc_obj.createElement('UL');

	for ( var var_name in obj )
	{
		var variable, li_elem;
		try
		{
			variable = obj[var_name];

			li_elem = ul_elem.appendChild(
				doc_obj.createElement('LI')
			);
			span_elem = li_elem.appendChild(
				doc_obj.createElement('SPAN')
			);
			span_elem.appendChild(
				doc_obj.createTextNode(var_name + " => " + variable)
			);
			Util.Event.add_event_listener(span_elem, 'click', function (event) { open_or_close_print_r_ul(event, variable); });
			//span_elem.onclick = open_or_close_print_r_ul;

			var typeof_variable = typeof(variable);
			if ( typeof_variable == "object" &&
				 !( typeof_variable == "string" ||
					typeof_variable == "boolean" ||
					typeof_variable == "number" ) )
			{
				if ( max_deepness > 1 )
				{
					li_elem.appendChild(
						Util.Object.get_print_r_chunk(variable, doc_obj, win, max_deepness - 1)
					);
				}
			}
		}
		catch(e)
		{
			// Only stop for fatal errors, because some properties when
			// accessed will always throw an error, and to die for
			// all of these would make print_r useless.
			if ( e.name != 'InternalError' )
			{
				ul_elem.appendChild(
					doc_obj.createElement('LI')
				).appendChild(
					doc_obj.createTextNode(var_name + " => [[[Exception thrown: " + e.message + "]]]")
				);
			}
			else
			{
				throw e;
			}
		}
	}
	return ul_elem;
};
